package com.yugao.fintech.draper.admin.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yugao.fintech.draper.admin.convert.RoleConvert;
import com.yugao.fintech.draper.admin.entity.SysRole;
import com.yugao.fintech.draper.admin.entity.SysRoleMenu;
import com.yugao.fintech.draper.admin.mapper.RoleMapper;
import com.yugao.fintech.draper.admin.param.DeptDTO;
import com.yugao.fintech.draper.admin.param.RoleDTO;
import com.yugao.fintech.draper.admin.param.RoleQuery;
import com.yugao.fintech.draper.admin.param.RoleVO;
import com.yugao.fintech.draper.admin.service.*;
import com.yugao.fintech.draper.auth.dto.UserInfoVO;
import com.yugao.fintech.draper.core.enums.RoleEnum;
import com.yugao.fintech.draper.core.model.DeptDataScopeDTO;
import com.yugao.fintech.draper.datascope.enums.DataScopeEnum;
import com.yugao.fintech.draper.core.exception.BaseException;
import com.yugao.fintech.draper.security.SecurityUser;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.yugao.fintech.draper.core.constant.RedisKeyConstants.*;

@Slf4j
@Service
@RequiredArgsConstructor
public class RoleServiceImpl extends ServiceImpl<RoleMapper, SysRole> implements RoleService {
    @Autowired
    private UserService userService;
    @Autowired
    private RoleMenuService roleMenuService;
    @Autowired
    private UserRoleService userRoleService;
    @Autowired
    private DeptService deptService;
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 分页查询角色信息
     */
    @Override
    public IPage<RoleDTO> pageQuery(RoleQuery roleQuery) {
        LambdaQueryWrapper<SysRole> wrapper = Wrappers.<SysRole>lambdaQuery()
                .like(StringUtils.isNotEmpty(roleQuery.getRoleName()), SysRole::getRoleName, roleQuery.getRoleName());
        Page<SysRole> pageInfo = baseMapper.selectPage(Page.of(roleQuery.getPageNum(), roleQuery.getPageSize()), wrapper);
        return RoleConvert.INSTANCE.convert(pageInfo);
    }

    /**
     * 添加角色信息
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public RoleDTO addRole(RoleDTO roleDTO) {
        // 保存角色信息
        SysRole sysRole = RoleConvert.INSTANCE.convert(roleDTO);
        int count = baseMapper.insert(sysRole);
        Long id = count != 1 ? null : sysRole.getRoleId();
        if (Objects.isNull(id)) {
            throw new BaseException("保存角色信息失败！");
        }
        // 保存角色菜单
        saveRoleMenu(roleDTO.setRoleId(id));
        return roleDTO;
    }

    /**
     * 根据id获取角色信息
     */
    @Cacheable(value = CACHE_PREFIX_ROLE, key = "#id", unless = "#result==null")
    @Override
    public RoleDTO getRoleById(Long id) {
        RoleVO role = baseMapper.getRoleById(id);
        return RoleConvert.INSTANCE.convert(role);
    }

    /**
     * 修改角色信息
     */
    @CacheEvict(value = CACHE_PREFIX_ROLE, key = "#roleDTO.roleId")
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean editRole(RoleDTO roleDTO) {
        baseMapper.updateById(RoleConvert.INSTANCE.convert(roleDTO));
        // 删除角色原有菜单
        deleteRoleMenuByRoleId(roleDTO.getRoleId());
        // 保存角色菜单
        saveRoleMenu(roleDTO);
        return Boolean.TRUE;
    }

    /**
     * 删除角色信息
     */
    @CacheEvict(value = CACHE_PREFIX_ROLE, key = "#id")
    @Override
    public Boolean deleteByRoleId(Long id) {
        RoleVO roleVO = baseMapper.getRoleById(id);
        if (RoleEnum.isSystem(roleVO.getCode())) {
            throw new BaseException("系统内置角色不可删除！");
        }
        // 删除角色菜单关联信息
        deleteRoleMenuByRoleId(id);
        // 删除角色用户关联信息
        userRoleService.deleteUserRoleByRoleId(id);
        // 删除角色信息
        return baseMapper.deleteById(id) > 0;
    }

    /**
     * 获取当前用户角色下的数据权限
     */
    @Cacheable(value = CACHE_PREFIX_ROLE_DATA_SCOPE, key = "#user.userId", unless = "#result==null")
    @Override
    public DeptDataScopeDTO getDataPermission(SecurityUser user) {
        DeptDataScopeDTO result = new DeptDataScopeDTO();
        UserInfoVO loginUser = userService.findByUserName(user.getUsername());
        if (StrUtil.isNotBlank(loginUser.getRoleId())) {
            String[] roleIds = loginUser.getRoleId().split(StringPool.COMMA);
            if (roleIds.length > 0) {
                Arrays.stream(roleIds).forEach(roleId -> {
                    String key = CACHE_PREFIX_ROLE + roleId;
                    RoleDTO roleDTO = (RoleDTO) redisTemplate.opsForValue().get(key);
                    if (ObjectUtil.isNull(roleDTO)) {
                        roleDTO = getRoleById(Long.valueOf(roleId));
                    }
                    // 为空时，跳过
                    if (ObjectUtil.isNull(roleDTO.getDataScope())) {
                        return;
                    }
                    // 情况一，ALL
                    if (Objects.equals(roleDTO.getDataScope(), DataScopeEnum.ALL.getScope())) {
                        result.setAll(true);
                        return;
                    }
                    // 情况二，DEPT_CUSTOM
                    if (Objects.equals(roleDTO.getDataScope(), DataScopeEnum.DEPT_CUSTOM.getScope())) {
                        CollectionUtil.addAll(result.getDeptIds(), roleDTO.getDataScopeDeptIds());
                        // 自定义可见部门时，保证可以看到自己所在的部门。否则，一些场景下可能会有问题。
                        // 例如说，登录时，基于 t_user 的 username 查询会可能被 dept_id 过滤掉
                        CollectionUtil.addAll(result.getDeptIds(), loginUser.getDeptId());
                        return;
                    }
                    // 情况三，DEPT_ONLY
                    if (Objects.equals(roleDTO.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
                        result.getDeptIds().add(loginUser.getDeptId());
                        return;
                    }
                    // 情况四，DEPT_DEPT_AND_CHILD
                    if (Objects.equals(roleDTO.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
                        List<DeptDTO> depts = deptService.listDeptsByParentId(loginUser.getDeptId());
                        CollectionUtil.addAll(result.getDeptIds(),
                                depts.stream().map(DeptDTO::getDeptId).collect(Collectors.toList()));
                        // 添加本身部门id
                        CollectionUtil.addAll(result.getDeptIds(), loginUser.getDeptId());
                        return;
                    }
                    // 情况五，SELF
                    if (Objects.equals(roleDTO.getDataScope(), DataScopeEnum.SELF.getScope())) {
                        result.setSelf(true);
                        return;
                    }
                    // 未知情况，error log 即可
                    log.error("[getDeptDataPermission][LoginUser({}) roleDO({}) 无法处理]", loginUser.getUserId(), result);
                });
            }
        }
        return result;
    }

    /**
     * 是否是超级管理员
     *
     * @param roleId 角色id
     */
    @Cacheable(value = CACHE_PREFIX_IS_SUPER_ADMIN, key = "#roleId")
    @Override
    public boolean isSuperAdmin(String roleId) {
        if (StrUtil.isBlank(roleId)) {
            return false;
        }
        String[] roleIds = roleId.split(StringPool.COMMA);
        List<SysRole> sysRoleList = baseMapper.selectBatchIds(Arrays.asList(roleIds));
        return sysRoleList.stream().anyMatch(r -> RoleEnum.SUPER_ADMIN.getType().equals(r.getCode()));
    }

    /**
     * 获取所有角色
     */
    @Cacheable(value = CACHE_PREFIX_ROLE)
    @Override
    public List<RoleDTO> listAll() {
        List<SysRole> roles = baseMapper.selectList(Wrappers.emptyWrapper());
        return RoleConvert.INSTANCE.convert(roles);
    }

    /**
     * 根据角色id删除角色菜单信息
     *
     * @param id 角色id
     */

    private void deleteRoleMenuByRoleId(Long id) {
        roleMenuService.deleteRoleMenuByRoleId(id);
    }

    /**
     * 保存角色菜单
     */
    private void saveRoleMenu(RoleDTO roleDTO) {
        String menuId = roleDTO.getMenuId();
        if (StrUtil.isBlank(menuId)) {
            return;
        }
        List<SysRoleMenu> sysRoleMenuList = Arrays.stream(menuId.split(StringPool.COMMA)).map(item -> {
            SysRoleMenu sysRoleMenu = new SysRoleMenu();
            sysRoleMenu.setRoleId(roleDTO.getRoleId());
            sysRoleMenu.setMenuId(Long.valueOf(item));
            return sysRoleMenu;
        }).collect(Collectors.toList());
        roleMenuService.addRoleMenuList(sysRoleMenuList);
    }
}
